<?php declare(strict_types=1);
/**
 * @author      xianganyall <xianganyall@gmail.com>
 * @copyright   2023-2025 owner
 **/

namespace Srv\Libs\Frame;

use Srv\Libs\Common\CommTime;
use Srv\Libs\ConfModel\TokenKeyConf;

final class TokenKey
{
    public const ATTR_AUTH_ID                       = 0;        // 附加属性认证数据
    public const ATTR_DEV_ID                        = 1;        // 附加属性设备ID
    public const ATTR_DEV_PLATFORM                  = 2;        // 附加属性设备类型
    public const ATTR_AUTH_SUPER                    = 3;        // 附加属性是否超级管理员认证
    public const RAND_MIN_NUM                       = 1000000;  // 随机数最小值
    public const RAND_MAX_NUM                       = 9999999;  // 随机数最大值
    private static ?TokenKeyConf $TokenKeyConf      = null;     // TokenKeyConf

    /**
     * 初始化
     */
    private static function init():void
    {
        if(!(self::$TokenKeyConf instanceof TokenKeyConf)) self::$TokenKeyConf = Conf::getTokenKeyConf();
    }

    /**
     * @return int
     * 计算有效期
     */
    private static function getExpireTime():int
    {
        if(self::$TokenKeyConf->getExpireTime() < 1) return 0;
        return CommTime::getTimeStamp(0) + self::$TokenKeyConf->getExpireTime();
    }

    /**
     * @return int
     * 获取随机数值
     */
    private static function getRandomNum():int
    {
        return mt_rand(self::RAND_MIN_NUM, self::RAND_MAX_NUM);
    }

    /**
     * @param int $uId
     * @param string $tokenKey
     * @param string $authStr
     * @param string $devId
     * @param string $devPlatform
     * @param bool $authSuper
     * @return string
     * 生成令牌数据值
     */
    public static function generateToken(int $uId, string $tokenKey, string $authStr, string $devId, string $devPlatform, bool $authSuper = false):string
    {
        if($uId < 1 || strlen($tokenKey) < 1) return '';
        self::init();
        if(strlen(self::$TokenKeyConf->getAppId()) !== 8 || strlen(self::$TokenKeyConf->getAppSecret()) !== 32) return '';
        return self::encodeToken(self::$TokenKeyConf->getAppId(), self::$TokenKeyConf->getAppSecret(), $tokenKey, self::getExpireTime(), self::getRandomNum(), $uId, [self::ATTR_AUTH_ID => $authStr, self::ATTR_DEV_ID => $devId, self::ATTR_DEV_PLATFORM => $devPlatform, self::ATTR_AUTH_SUPER => $authSuper ? '1' : '0']);
    }

    /**
     * @param string $tokenString
     * @param int $uId
     * @param string $tokenKey
     * @param string $authStr
     * @param string $devId
     * @param string $devPlatform
     * @param string $devIdCurr
     * @param string $devPlatformCurr
     * @param bool $authSuper
     * @return bool
     * 解码令牌数据值
     */
    public static function parseToken(string $tokenString, int &$uId, string &$tokenKey, string &$authStr, string &$devId, string &$devPlatform, string $devIdCurr, string $devPlatformCurr, bool &$authSuper):bool
    {
        if(strlen($tokenString) < 32) return false;
        self::init();
        $tokenKeyAppId      = self::$TokenKeyConf->getAppId();
        $tokenKeyAppSecret  = self::$TokenKeyConf->getAppSecret();
        if(strlen($tokenKeyAppId) !== 8 || strlen($tokenKeyAppSecret) !== 32) return false;
        $posInt             = strpos($tokenString, '|');
        if($posInt < 1) return false;
        $uIdStr             = substr($tokenString, 0, $posInt);
        $uId                = intval($uIdStr);
        if($uId < 1) return false;
        $posInt             += 1;
        $tokenKey           = substr($tokenString, $posInt, 16);
        $posInt             += 16;
        $version            = substr($tokenString, $posInt, 3);
        $posInt             += 3;
        $appId              = substr($tokenString, $posInt, 8);
        if($appId !== $tokenKeyAppId){  // 兼容后备解码
            $secretList                     = self::$TokenKeyConf->getSecretList();
            if(count($secretList) > 0){
                $tokenKeyAppId              = '';
                $tokenKeyAppSecret          = '';
                foreach($secretList as $secretListAppId => $secretListAppSecret){
                    if($appId === $secretListAppId){
                        $tokenKeyAppId      = $secretListAppId;
                        $tokenKeyAppSecret  = $secretListAppSecret;
                        break;
                    }
                }
                if(strlen($tokenKeyAppId) !== 8 || strlen($tokenKeyAppSecret) !== 32) return false;
            }else{
                return false;
            }
        }
        $posInt             += 8;
        $base64Decode       = self::getDeBase64(substr($tokenString, $posInt), $version);
        $base64DecodeLength = strlen($base64Decode);
        if($base64DecodeLength < 1) return false;
        $basePosInt         = 0;
        if(($basePosInt+2) > $base64DecodeLength) return false;
        $sigSize            = intval(@unpack('v', substr($base64Decode, $basePosInt, 2))[1]);
        $basePosInt         += 2;
        if(($basePosInt+$sigSize) > $base64DecodeLength) return false;
        $sig                = substr($base64Decode, $basePosInt, $sigSize);
        $basePosInt         += $sigSize;
        $tokenKeyCrc32Old   = crc32($tokenKey) & 0xffffffff;
        $uIdCrc32Old        = crc32($uIdStr) & 0xffffffff;
        if(($basePosInt+4) > $base64DecodeLength) return false;
        $tokenKeyCrc32      = intval(@unpack('V', substr($base64Decode, $basePosInt, 4))[1]);
        if($tokenKeyCrc32 !== $tokenKeyCrc32Old) return false;
        $basePosInt         += 4;
        if(($basePosInt+4) > $base64DecodeLength) return false;
        $uIdCrc32           = intval(@unpack('V', substr($base64Decode, $basePosInt, 4))[1]);
        if($uIdCrc32 !== $uIdCrc32Old) return false;
        $basePosInt         += 4;
        if(($basePosInt+2) > $base64DecodeLength) return false;
        $msgEncodeLength    = intval(@unpack('v', substr($base64Decode, $basePosInt, 2))[1]);
        $basePosInt         += 2;
        if(($basePosInt+$msgEncodeLength) > $base64DecodeLength) return false;
        $msgEncode          = substr($base64Decode, $basePosInt, $msgEncodeLength);
        $msgList            = array_map('ord', str_split($msgEncode, 1));
        $sigOld             = self::getSigString($appId, $tokenKeyAppSecret, $tokenKey, $uIdStr, $msgList);
        if($sig !== $sigOld) return false;
        $attrList           = [];
        $expireTime         = 0;
        $randomNum          = 0;
        if(!self::decodeMsgData($msgEncode, $attrList, $expireTime, $randomNum)) return false;
        if(isset($attrList[self::ATTR_AUTH_ID])) $authStr = $attrList[self::ATTR_AUTH_ID];
        if(isset($attrList[self::ATTR_DEV_ID])) $devId = $attrList[self::ATTR_DEV_ID];
        if(isset($attrList[self::ATTR_DEV_PLATFORM])) $devPlatform = $attrList[self::ATTR_DEV_PLATFORM];
        if(isset($attrList[self::ATTR_AUTH_SUPER])) $authSuper = $attrList[self::ATTR_AUTH_SUPER] === '1';
        if((self::$TokenKeyConf->isValidDevId() && $devId !== $devIdCurr) || (self::$TokenKeyConf->isValidDevPlatform() && $devPlatform !== $devPlatformCurr)) return false;
        if($randomNum < self::RAND_MIN_NUM || $randomNum > self::RAND_MAX_NUM || ($expireTime > 0 && $expireTime <= CommTime::getTimeStamp(0))) return false;
        return true;
    }

    /**
     * @param string $msgEncode
     * @param array $attrList
     * @param int $expireTime
     * @param int $randomNum
     * @return bool
     * 解密扩展数据
     */
    private static function decodeMsgData(string $msgEncode, array &$attrList, int &$expireTime, int &$randomNum):bool
    {
        $msgEncodeLength    = strlen($msgEncode);
        if($msgEncodeLength < 10) return false;
        $msgPosInt          = 0;
        if(($msgPosInt+4) > $msgEncodeLength) return false;
        $randomNum          = intval(@unpack('V', substr($msgEncode, $msgPosInt, 4))[1]);
        $msgPosInt          += 4;
        if(($msgPosInt+4) > $msgEncodeLength) return false;
        $expireTime         = intval(@unpack('V', substr($msgEncode, $msgPosInt, 4))[1]);
        $msgPosInt          += 4;
        $attrList           = [];
        if(($msgPosInt+2) > $msgEncodeLength) return false;
        $attrListSize       = intval(@unpack('v', substr($msgEncode, $msgPosInt, 2))[1]);
        $msgPosInt          += 2;
        $attrListIndex      = 0;
        while($attrListIndex < $attrListSize){
            if(($msgPosInt+2) > $msgEncodeLength) return false;
            $attrKey        = intval(@unpack('v', substr($msgEncode, $msgPosInt, 2))[1]);
            $msgPosInt      += 2;
            if(($msgPosInt+2) > $msgEncodeLength) return false;
            $attrValSize    = intval(@unpack('v', substr($msgEncode, $msgPosInt, 2))[1]);
            $msgPosInt      += 2;
            if(($msgPosInt+$attrValSize) > $msgEncodeLength) return false;
            $attrVal        = substr($msgEncode, $msgPosInt, $attrValSize);
            $msgPosInt      += $attrValSize;
            $attrList[$attrKey] = $attrVal;
            ++$attrListIndex;
        }
        return true;
    }

    /**
     * @param int $expireTime
     * @param int $randomNum
     * @param array $attrList
     * @return array
     * 加密扩增数据
     */
    private static function encodeMsgData(int $expireTime, int $randomNum, array $attrList):array
    {
        $attrListSize   = count($attrList);
        $buffer         = unpack('C*', pack('V', $randomNum));
        $buffer         = array_merge($buffer, unpack('C*', pack('V', $expireTime)));
        $buffer         = array_merge($buffer, unpack('C*', pack('v', $attrListSize)));
        if($attrListSize > 0) foreach($attrList as $key => $val){
            if(!is_string($val)) $val = strval($val);
            $buffer     = array_merge($buffer, unpack('C*', pack('v', $key)));
            $valUnPack  = unpack('C*', $val);
            $buffer     = array_merge($buffer, unpack('C*', pack('v', count($valUnPack))), $valUnPack);
        }
        return $buffer;
    }

    /**
     * @param string $appId
     * @param string $appSecret
     * @param string $tokenKey
     * @param int $expireTime
     * @param int $randomNum
     * @param int $uId
     * @param array $attrList
     * @return string
     * 加密令牌
     */
    private static function encodeToken(string $appId, string $appSecret, string $tokenKey, int $expireTime, int $randomNum, int $uId, array $attrList):string
    {
        $msgList        = self::encodeMsgData($expireTime, $randomNum, $attrList);
        $msgListSize    = count($msgList);
        $uIdStr         = strval($uId);
        $sig            = self::getSigString($appId, $appSecret, $tokenKey, $uIdStr, $msgList);
        $tokenKeyCrc32  = crc32($tokenKey) & 0xffffffff;
        $uIdCrc32       = crc32($uIdStr) & 0xffffffff;
        $content        = array_merge(unpack('C*', pack('v', strlen($sig)).$sig), unpack('C*', pack('V', $tokenKeyCrc32)), unpack('C*', pack('V', $uIdCrc32)), unpack('C*', pack('v', $msgListSize)), $msgList);
        $version        = self::getRandVersion();
        return $uIdStr.'|'.$tokenKey.$version.$appId.self::getEnBase64(implode(array_map('chr', $content)), $version);
    }

    /**
     * @param string $appId
     * @param string $appSecret
     * @param string $tokenKey
     * @param string $uIdStr
     * @param array $msgList
     * @return string
     * 计算签名
     */
    private static function getSigString(string $appId, string $appSecret, string $tokenKey, string $uIdStr, array $msgList):string
    {
        $sigString      = array_merge(unpack('C*', $appId), unpack('C*', $tokenKey), unpack('C*', $uIdStr), $msgList);
        return  hash_hmac('sha256', implode(array_map('chr', $sigString)), $appSecret, true);
    }

    /**
     * @param string $deString
     * @param string $version
     * @return string
     * 加密数据
     */
    private static function getEnBase64(string $deString, string $version):string
    {
        if(strlen($deString) < 1) return $deString;
        $enString               = base64_encode($deString);
        $enStringLength         = strlen($enString);
        $splitSize              = 0;
        switch ($version){
            case '101':{ $splitSize = 2; break; }
            case '102':{ $splitSize = 3; break; }
            case '103':{ $splitSize = 5; break; }
            case '104':{ $splitSize = 1; break; }
        }
        if($splitSize > 0){
            if($enStringLength >= ($splitSize*2)){
                $indexMax   = intval($enStringLength/($splitSize*2))*2;
                $enArray    = [];
                foreach (str_split($enString, $splitSize) as $index => $itemString) if($index < $indexMax) $enArray[$index+($index%2===0?1:-1)] = $itemString;
                ksort($enArray);
                $enString   = implode('', $enArray).substr($enString, $indexMax*$splitSize);
            }
        }
        return $enString;
    }

    /**
     * @param string $enString
     * @param string $version
     * @return string
     * 解密数据
     */
    private static function getDeBase64(string $enString, string $version):string
    {
        $enStringLength         = strlen($enString);
        if($enStringLength < 1) return $enString;
        $splitSize              = 0;
        switch ($version){
            case '101':{ $splitSize = 2; break; }
            case '102':{ $splitSize = 3; break; }
            case '103':{ $splitSize = 5; break; }
            case '104':{ $splitSize = 1; break; }
        }
        if($splitSize > 0){
            if($enStringLength >= ($splitSize*2)){
                $indexMax   = intval($enStringLength/($splitSize*2))*2;
                $enArray    = [];
                foreach (str_split($enString, $splitSize) as $index => $itemString) if($index < $indexMax) $enArray[$index+($index%2===0?1:-1)] = $itemString;
                ksort($enArray);
                $enString   = implode('', $enArray).substr($enString, $indexMax*$splitSize);
            }
        }
        return base64_decode($enString);
    }

    /**
     * @return string
     * 获取随机加解密版本
     */
    private static function getRandVersion():string
    {
        $allowVersionList   = ['101', '102', '103', '104'];
        return $allowVersionList[mt_rand(0, 3)]??'101';
    }
}